﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Tron.Players.Search
{

    /**
     * <summary>This heuristic considers how many blocks are reachable
     * from agentLoc and opponentLoc.
     */
    class ReachabilityHeuristic : IHeuristic
    {
        private static readonly int VISITED = 99;

        public double Evaluate(int[,] board, int depth, Vector2 agentLoc, Vector2 opponentLoc, bool opponentAlive)
        {
            // make a copy of the board so that we don't change it

            int columns = board.GetLength(0);
            int rows = board.GetLength(1);

            int[,] boardCopy = new int[columns, rows];

            for (int x = 0; x < columns; x++)
                for (int y = 0; y < rows; y++)
                    boardCopy[x, y] = board[x, y];

            // calculate how many blocks are reachable from the
            // agent's location via dfs.
            int agentReachable = dfs(boardCopy, agentLoc);

            // reset the board copy
            for (int x = 0; x < columns; x++)
                for (int y = 0; y < rows; y++)
                    boardCopy[x, y] = board[x, y];

            // calculate how many blocks are reachable from the
            // opponent's location via dfs.
            int opponentReachable = dfs(boardCopy, opponentLoc);

            Console.WriteLine("ReachabilityHeuristic:");
            Console.WriteLine("  Agent can reach:    {0} blocks", agentReachable);
            Console.WriteLine("  Opponent can reach: {0} blocks", opponentReachable);

            // handle edge cases

            if (agentReachable == opponentReachable)
                return 0; // also covers case where they're both 0
            if (agentReachable == 0)
                return -1;
            if (opponentReachable == 0)
                return 1;

            // if agent can reach more blocks, then the agent will
            // most likely win (although I'm not sure that's *always*
            // the case), and vice versa.

            // subtract out two rows and columns for the wall border
            int nonWallBlocks = (rows - 2) * (columns - 2);

            return (agentReachable / (double)nonWallBlocks) - (opponentReachable / (double)nonWallBlocks);
        }

        private int dfs(int[,] board, Vector2 loc)
        {
            int reachable = 0;
            foreach (Vector2 possibleMoveDirections in Constants.PossibleMoveDirections)
            {
                Vector2 newLocation = new Vector2(loc.X + possibleMoveDirections.X, loc.Y + possibleMoveDirections.Y);

                reachable += dfs_visit(board, newLocation);
            }

            return reachable;
        }

        private int dfs_visit(int[,] board, Vector2 loc)
        {
            int reachable = 0;
            if (board[(int)loc.X, (int)loc.Y] == Constants.EMPTY_BLOCK)
            {
                reachable++;
                board[(int)loc.X, (int)loc.Y] = VISITED;
                foreach (Vector2 possibleMoveDirection in Constants.PossibleMoveDirections)
                {
                    Vector2 newLocation = new Vector2(loc.X + possibleMoveDirection.X, loc.Y + possibleMoveDirection.Y);
                    reachable += dfs_visit(board, newLocation);
                }
            }

            return reachable;
        }
    }
}
